/*
 * Copyright (C) 2018-2020 Alibaba Group Holding Limited
 */

#include <aos/debug.h>
#include <aos/kernel.h>
#include <drv/codec.h>
// #include "mstream_ao.h"
#include "avutil/common.h"
#include "output/ao_cls.h"
#include "output/ao.h"

#define TAG                    "oflao"

#define MIN(a, b) ((a) < (b) ? (a) : (b)) 

#define CODEC_BUF_SIZE  (16 * 2 * 50)

#define EVT_AO_WRITE_AVAILABLE  1
#define EVT_AO_BUF_EMPTY        (1 << 1)

static uint8_t codec_buf[CODEC_BUF_SIZE];
static aos_event_t evt_ao_output;
static codec_output_t ao_codec_hdl; 
static int ao_first_write = 0;      // need to buffer sufficient data when first
static int ao_inited = 0;

// char g_output_buf[16 * 2 * 1000 * 2];
// size_t g_output_buf_offset = 0;

static void codec_event_cb(int idx, codec_event_t event, void *arg)
{
    if (event == CODEC_EVENT_PERIOD_WRITE_COMPLETE) {
        // printf("write cmplt\n");
        aos_event_set(&evt_ao_output, EVT_AO_WRITE_AVAILABLE, AOS_EVENT_OR);
    } else if (event == CODEC_EVENT_WRITE_BUFFER_EMPTY) {
        printf("ao empty\n");
        aos_event_set(&evt_ao_output, EVT_AO_BUF_EMPTY, AOS_EVENT_OR);
    }
}

static int mao_get_vol(void)
{
    int lgain = 0, rgain = 0, gain;
    int vol;

    csi_codec_output_get_digital_left_gain(&ao_codec_hdl, &lgain);
    csi_codec_output_get_digital_left_gain(&ao_codec_hdl, &rgain);

    gain = (lgain + rgain) / 2;
    vol = gain < -400 ? 0 : (gain + 400) / 5;

    LOGD(TAG, "get vol %d", vol);
    return vol;
}

static int mao_set_vol(int vol)
{
    aos_check_return_einval(vol >= 0 && vol <= 100);

    int gain = vol == 0 ? -955 : (vol - 80) * 5;

    csi_codec_output_set_digital_left_gain(&ao_codec_hdl, gain);
    csi_codec_output_set_digital_left_gain(&ao_codec_hdl, gain);

    return 0;
}

static int mao_init(void)
{
    aos_event_new(&evt_ao_output, 0);

#if defined(CONFIG_IR_DEMO) && CONFIG_IR_DEMO
    mao_set_vol(80);
#else
    mao_set_vol(40);
#endif

    return 0;
}

static int mao_open(int bits_per_sample, int sample_rate, int num_channels)
{
    int ret;
	codec_output_config_t config;
	
    LOGD(TAG, "mao_open bits=%d, rate=%d, channel=%d", bits_per_sample, sample_rate, num_channels);

	ao_codec_hdl.buf = codec_buf;
	ao_codec_hdl.buf_size = CODEC_BUF_SIZE ; //16*period
    ao_codec_hdl.period = 16;            //8*1*2 mono channel 8 *frame,frame=2*samples(left+right) samples= 2byte(16bit)
	ao_codec_hdl.cb = codec_event_cb;
	ret = csi_codec_output_open(&ao_codec_hdl);
    CHECK_RET_TAG_WITH_GOTO(ret == 0, ERR);

	config.bit_width = bits_per_sample;
	config.sample_rate = sample_rate;
	config.mono_mode_en = num_channels == 1 ? 1 : 0;

    // printf("start to set gain\n");

    LOGD(TAG, "codec ao open success");
    return 0;

ERR:
    LOGD(TAG, "codec ao open failed");
    // mao_stop();
    // mao_close();
    return -1;
}

static int mao_start(void)
{
    int ret = csi_codec_output_start(&ao_codec_hdl);
    if (ret < 0) {
        LOGD(TAG, "codec output start failed");
        // mao_stop();
        // mao_close();
        return -1;
    }

    aos_event_set(&evt_ao_output, EVT_AO_WRITE_AVAILABLE, AOS_EVENT_OR);
    LOGD(TAG, "codec ao start success");
    return 0;
}

static uint32_t mao_write(uint8_t *buf, uint32_t count)
{
    unsigned int flags = 0;
    uint32_t ret = 0;

    // LOGD(TAG, "w %u", count);

    if (csi_codec_output_buf_avail(&ao_codec_hdl) >= count) {
        ret = csi_codec_output_write(&ao_codec_hdl, buf, count);
    } else {
        while (1) {
            aos_event_get(&evt_ao_output, EVT_AO_WRITE_AVAILABLE | EVT_AO_BUF_EMPTY, AOS_EVENT_OR_CLEAR, &flags, AOS_WAIT_FOREVER);
            if (((flags & EVT_AO_WRITE_AVAILABLE) || (flags & EVT_AO_BUF_EMPTY)) && (csi_codec_output_buf_avail(&ao_codec_hdl) >= count)) {
                ret = csi_codec_output_write(&ao_codec_hdl, buf, count);
                break;
            }
        }
    }

    if (ret <= 0) {
        LOGD(TAG, "codec write failed");
        // mao_stop();
        // mao_close();
    }

    return ret;
}

/* pause playing */
static int mao_stop(void)
{
    csi_codec_output_stop(&ao_codec_hdl);
}

static int mao_pause(void)
{
    csi_codec_output_pause(&ao_codec_hdl);
}

static int mao_drain(void)
{
    /* wait for codec buf empty*/
    /* TBD: w800 codec left some bytes in the buffer when they are less than ao_codec_hdl.period size */
    while (csi_codec_output_buf_avail(&ao_codec_hdl) >= CODEC_BUF_SIZE - ao_codec_hdl.period) {
        aos_msleep(10);
    }
}

/* finish playing */
static int mao_close(void)
{
    csi_codec_output_close(&ao_codec_hdl);
}

static int _ao_user_open(ao_cls_t *o, sf_t sf)
{
    unsigned int rate;
    // struct ao_alsa_priv *priv = NULL;

    // priv = aos_zalloc(sizeof(struct ao_alsa_priv));
    // CHECK_RET_TAG_WITH_RET(priv, -1);

    rate = sf_get_rate(sf);
    switch (rate) {
    case 8000 :
    case 16000:
    case 32000:

    case 12000:
    case 24000:
    case 48000:

    case 11025:
    case 22050:
    case 44100:
        break;
    default:
        LOGE(TAG, "rate is not support, rate = %d\n", rate);
        goto err;
    }

    o->sf   = sf;

    // mao_open(sf_get_bit(o->sf), sf_get_rate(o->sf), sf_get_channel(o->sf));
    // o->priv = priv;
    return 0;
err:
    // aos_free(priv);

    return -1;
}

static int _ao_user_start(ao_cls_t *o)
{
    // g_output_buf_offset = 0;

    if (!ao_inited) {
        mao_open(sf_get_bit(o->sf), sf_get_rate(o->sf), sf_get_channel(o->sf));
        ao_inited = 1;
        LOGD(TAG, " ao opened");
    }

    mao_start();

    return 0;
}

static int _ao_user_stop(ao_cls_t *o)
{
    // int rc = -1;
    // struct ao_alsa_priv *priv = o->priv;

    // mao_pause();
    return 0;
}

static int _ao_user_drain(ao_cls_t *o)
{
    struct ao_alsa_priv *priv = o->priv;

    mao_drain();
    return 0;
}

static int _ao_user_close(ao_cls_t *o)
{
    // struct ao_alsa_priv *priv = o->priv;
    if (ao_inited) {
        mao_close();
        ao_inited = 0;
    }

    return 0;
}

static int _ao_user_write(ao_cls_t *o, const uint8_t *buffer, size_t count)
{
    int ret = 0;
    // struct ao_alsa_priv *priv = o->priv;


    // ret = aos_pcm_writei(priv->pcm, buffer, aos_pcm_bytes_to_frames(priv->pcm, count));

    size_t total_len = count;
    while (total_len > 0) {
        uint32_t wt_bytes = MIN(total_len, CODEC_BUF_SIZE / 2);
        ret = mao_write((uint8_t *)(buffer + count - total_len), wt_bytes);
        if (ret <= 0) {
            break;
        }
        total_len -= ret;
    }

    // if (g_output_buf_offset + count <= sizeof(g_output_buf)) {
    //     memcpy(g_output_buf + g_output_buf_offset, buffer, count);
    //     g_output_buf_offset += count;
    // }

    return (total_len == 0)? count : -1;
}

const struct ao_ops ao_user_alsa = {
    .name          = "w800",

    .open          = _ao_user_open,
    .start         = _ao_user_start,
    .stop          = _ao_user_stop,
    .drain         = _ao_user_drain,
    .close         = _ao_user_close,
    .write         = _ao_user_write,
};

int ao_register_user()
{
    LOGD(TAG, "ao register w800");

    mao_init();
    return ao_ops_register(&ao_user_alsa);
}


